#!/usr/bin/env python

""" Luminous automated testing """

import glob
import os
import subprocess
import sys
import time

from operator import itemgetter

simple_log = '{0}/log/simple_log'.format(sys.path[0])
regression_log = '{0}/log/regression_log'.format(sys.path[0])
fuzz_log = '{0}/log/fuzz_log'.format(sys.path[0])
use_colours = True


""" colours a string using ANSI escape sequences """
def colourise(string, colour, formatting=()):
  global use_colours
  if not use_colours:
    return string
    
  string = str(string)

  colours = {
    'FAIL' : '\033[91m',
    'PASS' : '\033[92m',
    'WARN' : '\033[93m',    
    }
  end = '\033[0m'
  
  s = colours.get(colour, end) + string + end  
  if formatting == 'bold' or 'bold' in formatting:
    s = '\033[1m' + s + '\033[0;0m'    
  return s


def print_help():
  print """Usage: {0} [OPTIONS]

Options:    
  --dull
    Don't use coloured text.

  --language=lang1,lang2,lang3...
    Where applicable only the given languages are tested.
    Only implemented for the regression suite.
    
  --run-all
    Runs all available tests
    
  --run-fuzz
    Runs the fuzz tester (May take up to 5 minutes). This hits Luminous with
    sequences of random input. It is assumed that Luminous will be able to 
    handle it within the given time, therefore if it has not  exited after 
    5 minutes it is assumed that Luminous has gone into an infinite loop.
    
  --run-simple
    Run the simple tests
    
  --run-regression
    Run the regression testing suite
  
  --suppress=language1,language2,...,languagen
    (not yet implemented)
    
  --verbose
    Print 'OK' results as well as warnings and errors.
  """.format(sys.argv[0])
  
def parse_args():
  
  options = {
    'colours': True,
    'fuzz': False,
    'regression' : False,
    'simple' : False,
    'languages': [],
    'suppressions' : {},
    'verbose' : False
    }
  unrecognised = []
  for opt in sys.argv[1:]:
    try:
      if opt.startswith('--suppress='):
        langs = opt[opt.index('=')+1:]
        for l in langs.split(','):
          options['suppressions'][l] = True
      elif opt == '--run-simple':
        options['simple'] = True
      elif opt == '--run-regression':
        options['regression'] = True
      elif opt == '--run-fuzz':
        options['fuzz'] = True
      elif opt == '--dull':
        options['colours'] = False
      elif opt == '--verbose':
        options['verbose'] = True
      elif opt == '--help':
        print_help()
        sys.exit(0)
      elif opt.startswith('--language'):
        languages = opt[opt.find('=')+1:]
        options['languages'] += languages.split(',')
      elif opt == '--run-all':
        options['regression'] = options['fuzz'] = options['simple'] = True
      else:
        unrecognised += [opt]
      
    except Exception as e:
      print e
      continue
  
  if len(unrecognised):
    print 'Unrecognised options: ' + ','.join(unrecognised)
    print 'See --help'
    sys.exit(1)
  if not (options['simple'] or options['regression'] or options['fuzz']):
    print 'No tests selected. Running only simple test by default'
    options['simple'] = True
  
  return options

def run_simple():
  global simple_results, simple_log
  
  os.chdir(sys.path[0])
  os.chdir('./simple')
  tests = glob.iglob('*.php')
  
  results = []
  
  
  open(simple_log, 'w').close()

  for test in tests:
    # huh
    logfile = open(simple_log, 'a')
    logfile.write('##############################\n')
    logfile.write('# TEST: {0}\n'.format(test))
    logfile.write('# DATE: {0}\n'.format(time.strftime('%Y-%m-%d %H:%M:%S')))
    logfile.write('# stderr and stdout follows\n')
    logfile.close()
    logfile = open(simple_log, 'a')
    ret = subprocess.call(['php', test], stdout=logfile, stderr=logfile)
    logfile.close()
    logfile = open(simple_log, 'a')
    logfile.write('# END TEST: {0}\n'.format(test))
    logfile.write('# EXIT STATUS: {0} {1}\n'.format(ret, 
      '(success)' if not ret else '(failure)'))
    logfile.write('#-----------------------------\n')    
    logfile.close()    
    yield (test, ret)
    
  
  
""" returns a list of tuples,
  (PATH, STATUS)
  where status is 0, 1, or 2 indicating:
  0: pass
  1: warning,
  2: error
"""
def run_regressions():
  
  global regression_log
  global options
  
  
  os.chdir(sys.path[0])
  os.chdir('./regression/')
  
  
  # we're going to break this up so we can keep it flowing back to the UI
  # instead of a big update all at once
  # there is probably a better way than writing the output to disk but 
  # all the easy functions are deprecated :-\
  
  # these are going to be the files we check  
  def recurse_files(path):    
    files = []
    for f in os.listdir(path):
      if os.path.isdir(path + '/' + f) and not f.startswith('.'):
        files += recurse_files(path + '/' + f)        
      elif os.path.isfile(path + '/' + f) and not f.startswith('.'):
        files.append(path + '/' + f)
    return files
  dirs = filter(lambda x: os.path.isdir(x) and not x.startswith('.'), 
    os.listdir('.'))
  
  if options['languages']:
    unknown = filter(lambda l: l not in dirs, options['languages'])
    if unknown:
      print 'Regression test warning: the following languages are unknown:'
      print ','.join(unknown)
    dirs = filter(lambda l: l in options['languages'], dirs)
 
  files = []
  for d in dirs:
    files += recurse_files(d)
  files = filter(lambda x: not x.endswith('.luminous'), files)
  files.sort()

  real_log = open(regression_log, 'w')
  real_log.write('''# Regression testing log
# Each line will record a file and a status. 
# The status 'MISSING EXPECTED OUTPUT' indicates that output needs to be
# generated for that file.
# The status 'FAILED' indicates that Luminous did not get its expected
# output. A diff will have been left in the directory of the failed file
# called $FILENAME._diff.luminous

# You might prefer to run the test.php function in the regression 
# directory or at least consult the README in there.

# DATE: {0}


'''.format(time.strftime('%Y-%m-%d %H:%M:%S')))
  for f in files:
    templog = regression_log + '.temp'
    logfile = open(templog, 'w')
    ret = subprocess.call(['php', 'test.php', f], stdout=logfile)
    logfile.close()
    logfile = open(templog, 'r')  
    output = logfile.read()
    logfile.close()
    
    status = 0
    
    for line in output.split('\n'):
      line = line.strip()
      if not len(line): continue      
      if line.startswith('FAILURE'):        
        status = 2
        break
      elif line.startswith('WARNING'):
        linelower = line.lower()
        status = 1
        break
        
    status_str = ['OK', 'MISSING EXPECTED OUTPUT', 'FAILED'][status]
    real_log.write(status_str + ': ' + f + '\n')
    
    os.unlink(templog)
    yield (f, status)
  real_log.close()
  
  
def run_fuzz():
  global fuzz_log
  os.chdir(sys.path[0])  
  os.chdir('./other')
  logfile = open(fuzz_log, 'w')
  ret = subprocess.call(['php', 'fuzz.php'], stderr=logfile,stdout=logfile)
  logfile.close()
  return ret
  
  
options = parse_args()
use_colours = options.get('colours', True)
exit_status = 0
warnings = False

print 'Running tests...'



if (options['simple']):
  print 'Running simple tests...'
  failed = False
  for r in run_simple():
    if r[1] == 0:
      if options['verbose']:
        print '  ...{0} {1}'.format(r[0], colourise('OK', 'PASS'))
    else:
      print '  ...{0} {1}'.format(r[0], colourise('FAILED', 'FAIL'))
      failed = True
  if failed:
    exit_status = 1
    print 'Simple testing encountered errors.',
    print 'A log has been written to:\n  {0}'.format(simple_log)
  else:
    print 'Simple testing encountered no errors'
  print ''


if (options['regression']):  
  print 'Running regression tests...'
  
  res = run_regressions()
  warn = 0
  failed = False
  
  for r in res:    
    path, status = r
    status_summary = ''
    s = '  ...{0} {1}'
    if status == 0:
      if not options['verbose']:
        continue
      status_summary = colourise('OK', 'PASS')
    elif status == 1:
      warn += 1
      status_summary = colourise('WARNING', 'WARN')
    else:
      status_summary = colourise('FAILED', 'FAIL')
      failed = True
    print s.format(path, status_summary)

  if failed:
    exit_status = 1
    print 'Regression testing encountered errors'
  else:
    print 'Regression testing encountered no errors'
    
  if warn:    
    print '{0} warnings were encountered'.format(warn)
    warnings = True
    
  if warnings or failed:
    print 'Regression test log written to:\n  {0}'.format(regression_log)
  print ''
    
if (options['fuzz']):
  print 'Running fuzz tester (this may take a few minutes)...',
  sys.stdout.flush()
  
  fail = run_fuzz()
  if fail:
    print '{0}'.format(colourise('FAILED', 'FAIL'))
    print 'A log has been written to:\n  {0}'.format(fuzz_log)
    exit_status = 1
  else:
    print '{0}'.format(colourise('OK', 'PASS'))
  print ''
  

print ''
print 'Tests',
print ('passed' if exit_status == 0 else 'failed'),

if warnings:
  print '(with warnings)',
print ''

sys.exit(exit_status)
       
